package com.tang.framework.lock.support;


import com.tang.common.constant.lock.LockConstant;
import com.tang.common.utils.ReflectUtil;
import com.tang.common.utils.CoolUtil;
import com.tang.framework.core.ContextHold;
import com.tang.framework.core.model.LoginUser;
import com.tang.common.exception.BusinessException;
import com.tang.common.exception.eumes.Error;
import com.tang.framework.lock.ShardLock;
import com.tang.framework.lock.model.ParameterIndexFiled;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;


import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class ShardLockAspect {


    @Resource
    private HttpServletRequest request;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;


    @Resource
    private TransactionDefinition transactionDefinition;


    @Pointcut("@annotation(com.tang.framework.lock.ShardLock)")
    public void pointCut() {

    }


    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnObject = null;
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        if (method.isAnnotationPresent(ShardLock.class)) {
            log.info("进入锁业务方法");
            ShardLock shardLock = method.getAnnotation(ShardLock.class);
            if (shardLock.type().equals(ShardLock.Type.TENANT)) {
                log.info("是租户锁类型");
                String lockName = processLockName(joinPoint, shardLock);
                RLock methodLock = redissonClient.getLock(lockName);
                log.info("锁key：{}", lockName);
                TransactionStatus transaction = getTransaction(shardLock);
                try {
                    if (methodLock.tryLock(LockConstant.WAIT_TIME, LockConstant.LESS_TIME, TimeUnit.SECONDS)) {
                        log.info("获取锁成功");
                        returnObject = joinPoint.proceed(joinPoint.getArgs());
                        dataSourceTransactionManager.commit(transaction);
                        log.info("执行成功");
                    } else {
                        log.warn("获取锁失败");
                        throw new BusinessException(Error.OPERATE_ERROR);
                    }
                } catch (Exception e) {
                    log.warn("执行方法失败, 事务回滚");
                    dataSourceTransactionManager.rollback(transaction);
                    e.printStackTrace();
                } finally {
                    log.info("执行完成，释放锁");
                    methodLock.unlock();
                }
            }
        } else {
            returnObject = joinPoint.proceed(joinPoint.getArgs());
        }
        return returnObject;
    }


    /**
     * @param joinPoint
     * @param lock      分布式锁
     * @return
     */
    private String processLockName(ProceedingJoinPoint joinPoint, ShardLock lock) {
//        if (lock.type().equals(Lock.Type.TENANT)) {
//            return lock.value();
//        } else {
        return processExpressionLockName(joinPoint, lock);
//        }

    }

    /**
     * 根据表达式获取名
     *
     * @param joinPoint
     * @param shardLock
     * @return
     */
    private String processExpressionLockName(ProceedingJoinPoint joinPoint, ShardLock shardLock) {
        checkLockNameValue(shardLock.value(), shardLock.expressions(), shardLock.type());
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Map<String, List<ParameterIndexFiled>> expressionMap = getExpressionMap(methodSignature, shardLock.expressions());
        Object[] args = joinPoint.getArgs();
        List<String> expressionValueList = new ArrayList<>();
        expressionMap.forEach((k, v) -> {
            v.forEach(item -> {
                Object parameter = args[item.getIndex()];
                if (item.getObject()) {
                    expressionValueList.add(ReflectUtil.getFileValue(parameter, item.getFiled()).toString());
                } else if (parameter.getClass().isEnum()){
                    expressionValueList.add(ReflectUtil.getEnumValue(parameter, parameter.getClass()));
                }else{
                    expressionValueList.add(parameter.toString());
                }
            });
        });
        if (shardLock.type().equals(ShardLock.Type.TENANT)) {
            LoginUser loginUser = ContextHold.getLoginUser();
            List<String> valueList = new ArrayList<>();
            valueList.add(Objects.requireNonNull(loginUser).getTenantId());
            valueList.addAll(expressionValueList);
            return String.format(shardLock.value(), valueList.toArray());
        }
        return String.format(shardLock.value(), expressionValueList.toArray());

    }

    private Map<String, List<ParameterIndexFiled>> getExpressionMap(MethodSignature methodSignature, String[] expressions) {
        Map<String, List<ParameterIndexFiled>> parameterExpressionList = new LinkedHashMap<>();
        String[] parameterNames = methodSignature.getParameterNames();
        for (String expression : expressions) {

            String[] parameterFiled = expression.split("\\.");
            parameterExpressionList.compute(parameterFiled[0], (k, v) -> {
                if (Objects.isNull(v)) {
                    v = new ArrayList<>();
                }
                int index = CoolUtil.getIndex(parameterNames, parameterFiled[0]);
                if (!expression.contains(".")) {
                    v.add(new ParameterIndexFiled(index, "", false));
                } else {
                    v.add(new ParameterIndexFiled(index, parameterFiled[1], true));

                }
                return v;
            });
        }
        return parameterExpressionList;
    }

    private void checkLockNameValue(String value, String[] expressions, ShardLock.Type type) {
        int stringNum = CoolUtil.getStringNum(value, "%s");
        if (type.equals(ShardLock.Type.TENANT)) {
            if (expressions.length != stringNum - 1) {
                throw new BusinessException(Error.SHARD_LOCK_EXPRESSION_ERROR, value);
            }
        } else {
            if (expressions.length != stringNum) {
                throw new BusinessException(Error.SHARD_LOCK_EXPRESSION_ERROR, value);
            }
        }
    }


    private TransactionStatus getTransaction(ShardLock lock) {
        if (lock.defTransaction()) {
            return dataSourceTransactionManager.getTransaction(transactionDefinition);
        } else {
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            def.setPropagationBehavior(lock.propagationBehavior());
            def.setIsolationLevel(lock.isolationLevel());
            return dataSourceTransactionManager.getTransaction(def);
        }
    }

}
